/*
cAndy Apple Core: An Apple //e emulator engine
    - based almost entirely on AppleWin

Copyright (C) 1994-1996, Michael O'Brien
Copyright (C) 1999-2001, Oliver Schmidt
Copyright (C) 2002-2005, Tom Charlesworth
Copyright (C) 2006-2008, Tom Charlesworth, Michael Pohoreski, Nick Westgate
Copyright (C) 2009-2010, Garnet Ulrich

cAndy Apple Core is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

cAndy Apple Core is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with cAndy Apple Core; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "StdAfx.h"

#include "wwrapper.h"
#include "charset.h"

int g_videoDebug = 0;

JNIEnv* CAenv;
jintArray CAdisplaybuffer;

static BOOL CAvidlastmemExists = 0;

#define FLASH_80_COL 1

#define HALF_SHIFT_DITHER 0

#define  FRAMEBUFFER_W  560
#define  FRAMEBUFFER_H  384


// STANDARD WINDOWS COLORS
#define  CREAM            0xF6
#define  MEDIUM_GRAY      0xF7
#define  DARK_GRAY        0xF8
#define  RED              0xF9
#define  GREEN            0xFA
#define  YELLOW           0xFB
#define  BLUE             0xFC
#define  MAGENTA          0xFD
#define  CYAN             0xFE
#define  WHITE            0xFF


enum Color_Palette_Index_e
{
	// Really need to have Quarter Green and Quarter Blue for Hi-Res
	  BLACK
	, DARK_RED
	, DARK_GREEN       // Half Green
	, DARK_YELLOW
	, DARK_BLUE        // Half Blue
	, DARK_MAGENTA
	, DARK_CYAN
	, LIGHT_GRAY
	, MONEY_GREEN
	, SKY_BLUE

// OUR CUSTOM COLORS
	, DEEP_RED
	, LIGHT_BLUE
	, BROWN
	, ORANGE
	, PINK
	, AQUA

// CUSTOM HGR COLORS (don't change order) - For tv emulation g_nAppMode
	, HGR_BLACK
	, HGR_WHITE
	, HGR_BLUE
	, HGR_RED
	, HGR_GREEN
	, HGR_MAGENTA
	, HGR_GREY1
	, HGR_GREY2
	, HGR_YELLOW
	, HGR_AQUA
	, HGR_PURPLE
	, HGR_PINK

// USER CUSTOMIZABLE COLOR
	, MONOCHROME_CUSTOM

// Pre-set "Monochromes"
	, MONOCHROME_AMBER
	, MONOCHROME_GREEN
	, MONOCHROME_WHITE

	, NUM_COLOR_PALETTE
};

const int CA4ByteColour [NUM_COLOR_PALETTE] =
{   0x00000000, //BLACK 0
    0xFFD00030, //DEEP_RED 1
    0xFF000080, //DARK_BLUE 2
    0xFFFF00FF, //PURPLE 3
    0xFF008000, //DARK_GREEN 4
    0xFF808080, //DARK GRAY 5
    0xFF0000FF, //MEDIUM BLUE 6
    0xFF60A0FF, //LIGHT BLUE 7
    0xFF805000, //BROWN 8
    0xFFFF8000, //ORANGE 9
    0xFFC0C0C0, //LIGHT_GRAY A
    0xFFFF9080, //PINK B
    0xFF00FF00, //LIGHT GREEN C
    0xFFFFFF00, //DARK_YELLOW D
    0xFF40FF90, //AQUA E
    0xFFFFFFFF, //WHITE F
    0x00000000, //HGR_BLACK
    0xFFFFFFFF, //HGR_WHITE
    0xFF0080FF, //HGR_BLUE
    0xFFF05000, //HGR_RED
    0xFF20C000, //HGR_GREEN
    0xFFA000FF, //HGR_MAGENTA
    0xFF808080, //HGR_GREY1
    0xFF808080, //HGR_GREY2
    0xFFD0B0B0, //HGR_YELLOW
    0xFF20B0B0, //HGR_AQUA
    0xFF6050E0, //HGR_PURPLE
    0xFFD040A0, //HGR_PINK
    0xFFFF0000, //MONOCHROME_CUSTOM
    0xFFFF0000, //MONOCHROME_AMBER
    0xFFFF0000, //MONOCHROME_GREEN
    0xFFFF0000  //MONOCHROME_WHITE
};

const int SRCOFFS_40COL   = 0;
const int SRCOFFS_IIPLUS  = (SRCOFFS_40COL  +  256); //256
const int SRCOFFS_80COL   = (SRCOFFS_IIPLUS +  256); //512
const int SRCOFFS_LORES   = (SRCOFFS_80COL  +  128); //640
const int SRCOFFS_HIRES   = (SRCOFFS_LORES  +   16); //656
const int SRCOFFS_DHIRES  = (SRCOFFS_HIRES  +  512); //1168
const int SRCOFFS_TOTAL   = (SRCOFFS_DHIRES + 2560); //3728

enum VideoFlag_e
{
	VF_80COL  = 0x00000001,
	VF_DHIRES = 0x00000002,
	VF_HIRES  = 0x00000004,
	VF_MASK2  = 0x00000008,
	VF_MIXED  = 0x00000010,
	VF_PAGE2  = 0x00000020,
	VF_TEXT   = 0x00000040
};

#define  SW_80COL         (vidmode & VF_80COL)
#define  SW_DHIRES        (vidmode & VF_DHIRES)
#define  SW_HIRES         (vidmode & VF_HIRES)
#define  SW_MASK2         (vidmode & VF_MASK2)
#define  SW_MIXED         (vidmode & VF_MIXED)
#define  SW_PAGE2         (vidmode & VF_PAGE2)
#define  SW_TEXT          (vidmode & VF_TEXT)

#define  SETSOURCEPIXEL(x,y,c)  g_aSourceStartofLine[(y)][(x)] = (c)

// video scanner constants
int const kHBurstClock      =    53; // clock when Color Burst starts
int const kHBurstClocks     =     4; // clocks per Color Burst duration
int const kHClock0State     =  0x18; // H[543210] = 011000
int const kHClocks          =    65; // clocks per horizontal scan (including HBL)
int const kHPEClock         =    40; // clock when HPE (horizontal preset enable) goes low
int const kHPresetClock     =    41; // clock when H state presets
int const kHSyncClock       =    49; // clock when HSync starts
int const kHSyncClocks      =     4; // clocks per HSync duration
int const kNTSCScanLines    =   262; // total scan lines including VBL (NTSC)
int const kNTSCVSyncLine    =   224; // line when VSync starts (NTSC)
int const kPALScanLines     =   312; // total scan lines including VBL (PAL)
int const kPALVSyncLine     =   264; // line when VSync starts (PAL)
int const kVLine0State      = 0x100; // V[543210CBA] = 100000000
int const kVPresetLine      =   256; // line when V state presets
int const kVSyncLines       =     4; // lines per VSync duration

static BYTE          celldirty[40][32];
static COLORREF      customcolors[NUM_COLOR_PALETTE];	// MONOCHROME is last custom color

static LPBYTE        g_pHiresBank1;
static LPBYTE        g_pHiresBank0;

static LPBYTE        g_pSourcePixels;

const int MAX_SOURCE_Y = 512;
static LPBYTE        g_aSourceStartofLine[ MAX_SOURCE_Y ];
static LPBYTE        g_pTextBank1; // Aux
static LPBYTE        g_pTextBank0; // Main

static int       g_nAltCharSetOffset  = 0; // alternate character set

bool      g_bVideoDisplayPage2 = 0;
bool      g_VideoForceFullRedraw = 1;

int g_CARedrawFlag = 3;

static LPBYTE    framebufferaddr  = (LPBYTE)0;
static LONG      framebufferpitch = 0;
BOOL             graphicsmode     = 0;
static BOOL      hasrefreshed     = 0;
static DWORD     lastpageflip     = 0;
static BOOL      rebuiltsource    = 0;
static DWORD     dwVBlCounter     = 0;
static LPBYTE    vidlastmem       = NULL;
static DWORD     vidmode          = VF_TEXT;

DWORD     g_eVideoType        = VT_COLOR_STANDARD;

static bool g_bTextFlashState = false;
static bool g_bTextFlashFlag = false;

static bool bVideoScannerNTSC = true;  // NTSC video scanning (or PAL)

// Prototypes (Private) _____________________________________________

void DrawDHiResSource ();
void DrawHiResSource ();
void DrawHiResSourceHalfShiftFull ();
void DrawHiResSourceHalfShiftDim ();
void DrawLoResSource ();
void DrawMonoDHiResSource ();
void DrawMonoHiResSource ();
void DrawMonoLoResSource ();
void DrawMonoTextSource ();
void DrawTextSource ();

int GetMonochromeIndex();

//===========================================================================
void  CopySource (int destx, int desty, int xsize, int ysize, int sourcex, int sourcey) {
  LPBYTE currsourceptr = g_aSourceStartofLine[sourcey]+sourcex;   //now currsourceptr is aimed inside g_pSourcePixels

  int *intarray = (int *) CAenv->GetPrimitiveArrayCritical(CAdisplaybuffer, NULL /* isCopy */); //lock the jni array interface

  int bytesleft;
  BYTE eightbitcolour;
  int CAcurrentline = 0;
  int CAmodifiedDesty = desty / 2;
  while (ysize--)
  {
      bytesleft = xsize;
      while (bytesleft) {// this does a single line of the cell
          --bytesleft;
          eightbitcolour = *(currsourceptr+bytesleft);
          intarray[((CAcurrentline+CAmodifiedDesty)*560)+(destx+bytesleft)] = CA4ByteColour[eightbitcolour];
      }
      CAcurrentline++;
      currsourceptr -= SRCOFFS_TOTAL * 2; //bump it down two lines // we want 192 lines, not 384
      ysize--; //needs an extra bump to skip a line
  }

  CAenv->ReleasePrimitiveArrayCritical(CAdisplaybuffer, intarray, 0 /* mode */);  //unlock the jni array interface

}

//===========================================================================
void CreateDIBSections () //allocs and loads up the "source" bitmap - converted - gu
{

    g_pSourcePixels = (LPBYTE)VirtualAlloc(NULL,1908736,MEM_COMMIT,PAGE_READWRITE); //gu - MAX_SOURCE_Y (512) x SRCOFFS_TOTAL (3728) -why so big???

	// CREATE THE OFFSET TABLE FOR EACH SCAN LINE IN THE SOURCE IMAGE
	for (int y = 0; y < MAX_SOURCE_Y; y++)
		g_aSourceStartofLine[ y ] = g_pSourcePixels + SRCOFFS_TOTAL*((MAX_SOURCE_Y-1) - y);

	// DRAW THE SOURCE IMAGE INTO THE SOURCE BIT BUFFER
	ZeroMemory(g_pSourcePixels,SRCOFFS_TOTAL*512);

	if((g_eVideoType != VT_MONO_CUSTOM) &&
		(g_eVideoType != VT_MONO_AMBER ) &&
		(g_eVideoType != VT_MONO_GREEN ) &&
		(g_eVideoType != VT_MONO_WHITE ))
	{
		DrawTextSource();
		DrawLoResSource();

		if (g_eVideoType == VT_COLOR_HALF_SHIFT_DIM)
			DrawHiResSourceHalfShiftDim();
		else {
			DrawHiResSource();
		}
		DrawDHiResSource();
	}
	else
	{
		DrawMonoTextSource();
		DrawMonoLoResSource();
		DrawMonoHiResSource();
		DrawMonoDHiResSource();
	}


}
//===========================================================================
void DrawDHiResSource () {

    BYTE colorval[16] = {0,2,4,6,8,10,12,14,1,3,5,7,9,11,13,15}; //gu - these are the lores values from the table at the top of this file to match the
                                                                 //      colours listed above.  Not sure why I had to remap but they are all tested and working.
#define OFFSET  3
#define SIZE    10

  for (int column = 0; column < 256; column++) {
    int coloffs = SIZE * column;
    for (unsigned byteval = 0; byteval < 256; byteval++) {
      int color[SIZE];
      ZeroMemory(color,sizeof(color));
      unsigned pattern = MAKEWORD(byteval,column);
      int pixel;
      for (pixel = 1; pixel < 15; pixel++) {
        if (pattern & (1 << pixel)) {
          int pixelcolor = 1 << ((pixel-OFFSET) & 3);
          if ((pixel >=  OFFSET+2) && (pixel < SIZE+OFFSET+2) && (pattern & (0x7 << (pixel-4))))
            color[pixel-(OFFSET+2)] |= pixelcolor;
          if ((pixel >=  OFFSET+1) && (pixel < SIZE+OFFSET+1) && (pattern & (0xF << (pixel-4))))
            color[pixel-(OFFSET+1)] |= pixelcolor;
          if ((pixel >=  OFFSET+0) && (pixel < SIZE+OFFSET+0))
            color[pixel-(OFFSET+0)] |= pixelcolor;
          if ((pixel >=  OFFSET-1) && (pixel < SIZE+OFFSET-1) && (pattern & (0xF << (pixel+1))))
            color[pixel-(OFFSET-1)] |= pixelcolor;
          if ((pixel >=  OFFSET-2) && (pixel < SIZE+OFFSET-2) && (pattern & (0x7 << (pixel+2))))
            color[pixel-(OFFSET-2)] |= pixelcolor;
        }
      }

	  if (g_eVideoType == VT_COLOR_TEXT_OPTIMIZED)
	  {
		for (pixel = 0; pixel < 13; pixel++)
		{
		  if ((pattern & (0xF << pixel)) == (unsigned)(0xF << pixel))
			for (int pos = pixel; pos < pixel + 4; pos++)
			  if (pos >= OFFSET && pos < SIZE+OFFSET)
				color[pos-OFFSET] = 15;
		}
	  }

      int y = byteval << 1;
      for (int x = 0; x < SIZE; x++) {
        SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y  ,colorval[color[x]]);
        SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y+1,colorval[color[x]]);
      }
    }
  }

#undef SIZE
#undef OFFSET
}


enum ColorMapping
{
    CM_Magenta
    , CM_Blue
    , CM_Green
    , CM_Orange
    , CM_Black
    , CM_White
    , NUM_COLOR_MAPPING
};

const BYTE aColorIndex[ NUM_COLOR_MAPPING ] =
{
        HGR_MAGENTA
        , HGR_BLUE
        , HGR_GREEN
        , HGR_RED
        , HGR_BLACK
        , HGR_WHITE
};

const BYTE aColorDimmedIndex[ NUM_COLOR_MAPPING ] =
{
        DARK_MAGENTA, // <- HGR_MAGENTA
        DARK_BLUE   , // <- HGR_BLUE
        DARK_GREEN  , // <- HGR_GREEN
        DEEP_RED    , // <- HGR_RED
        HGR_BLACK   , // no change
        LIGHT_GRAY    // HGR_WHITE
};


//===========================================================================
void DrawHiResSourceHalfShiftDim ()
{
    for (int iColumn = 0; iColumn < 16; iColumn++)
    {
        int coloffs = iColumn << 5;

        for (unsigned iByte = 0; iByte < 256; iByte++)
        {
            int aPixels[11];

            aPixels[ 0] = iColumn & 4;
            aPixels[ 1] = iColumn & 8;
            aPixels[ 9] = iColumn & 1;
            aPixels[10] = iColumn & 2;

            int nBitMask = 1;
            int iPixel;
            for (iPixel  = 2; iPixel < 9; iPixel++) {
                aPixels[iPixel] = ((iByte & nBitMask) != 0);
                nBitMask <<= 1;
            }

            int hibit = ((iByte & 0x80) != 0);
            int x     = 0;
            int y     = iByte << 1;

            while (x < 28)
            {
                int adj = (x >= 14) << 1;
                int odd = (x >= 14);

                for (iPixel = 2; iPixel < 9; iPixel++)
                {
                    int color = CM_Black;
                    if (aPixels[iPixel])
                    {
                        if (aPixels[iPixel-1] || aPixels[iPixel+1])
                        {
                            color = CM_White;
                        }
                        else
                            color = ((odd ^ (iPixel&1)) << 1) | hibit;
                    }
                    else if (aPixels[iPixel-1] && aPixels[iPixel+1])
                    {
                        if (!(aPixels[iPixel-2] && aPixels[iPixel+2]))
                            color = ((odd ^ !(iPixel&1)) << 1) | hibit;
                    }

                    /*
						Address Binary   -> Displayed
						2000:01 0---0001 -> 1 0 0 0  column 1
						2400:81 1---0001 ->  1 0 0 0 half-pixel shift right
						2800:02 1---0010 -> 0 1 0 0  column 2

						2000:02 column 2
						2400:82 half-pixel shift right
						2800:04 column 3

						2000:03 0---0011 -> 1 1 0 0  column 1 & 2
						2400:83 1---0011 ->  1 1 0 0 half-pixel shift right
						2800:06 1---0110 -> 0 1 1 0  column 2 & 3

						@reference: see Beagle Bro's Disk: "Silicon Salad", File: DOUBLE HI-RES
						Fortunately double-hires is supported via pixel doubling, so we can do half-pixel shifts ;-)
                     */
                    switch (color)
                    {
                    case CM_Magenta:
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  , HGR_MAGENTA  ); // aColorIndex
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y  , DARK_MAGENTA ); // aColorDimmedIndex
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1, HGR_MAGENTA  ); // aColorIndex
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1, DARK_MAGENTA ); // aColorDimmedIndex
                        break;
                    case CM_Blue   :
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y  , HGR_BLUE  );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+2,y  , DARK_BLUE );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1, HGR_BLUE  );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+2,y+1, DARK_BLUE );
                        // Prevent column gaps
                        if (hibit)
                        {
                            if (iPixel <= 2)
                            {
                                SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  , DARK_BLUE );
                                SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1, DARK_BLUE );
                            }
                        }
                        break;
                    case CM_Green :
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  , HGR_GREEN  );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y  , DARK_GREEN );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1, HGR_GREEN  );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1, DARK_GREEN );
                        break;
                    case CM_Orange:
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y  , HGR_RED  );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+2,y  , BROWN ); // DARK_RED is a bit "too" red
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1, HGR_RED  );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+2,y+1, BROWN ); // DARK_RED is a bit "too" red
                        // Prevent column gaps
                        if (hibit)
                        {
                            if (iPixel <= 2)
                            {
                                SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  , BROWN ); // DARK_RED is a bit "too" red
                                SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1, BROWN ); // DARK_RED is a bit "too" red
                            }
                        }
                        break;
                    case CM_Black :
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  , HGR_BLACK );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y  , HGR_BLACK );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1, HGR_BLACK );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1, HGR_BLACK );
                        break;
                    case CM_White :
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  , HGR_WHITE );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y  , HGR_WHITE );
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1, HGR_WHITE ); // LIGHT_GRAY <- for that half scan-line look
                        SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1, HGR_WHITE ); // LIGHT_GRAY <- for that half scan-line look
                        // Prevent column gaps
                        if (hibit)
                        {
                            if (iPixel <= 2)
                            {
                                SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  , HGR_WHITE ); // LIGHT_GRAY HGR_GREY1
                                SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1, HGR_WHITE ); // LIGHT_GRAY HGR_GREY1
                            }
                        }
                        break;
                    default:
                        break;
                    }
                    x += 2;
                }
            }
        }
    }
}


//===========================================================================
void DrawHiResSource ()
{
	for (int iColumn = 0; iColumn < 16; iColumn++)
	{
		int coloffs = iColumn << 5;

		for (unsigned iByte = 0; iByte < 256; iByte++)
		{
			int aPixels[11];

			aPixels[ 0] = iColumn & 4;
			aPixels[ 1] = iColumn & 8;
			aPixels[ 9] = iColumn & 1;
			aPixels[10] = iColumn & 2;

			int nBitMask = 1;
			int iPixel;
			for (iPixel  = 2; iPixel < 9; iPixel++) {
				aPixels[iPixel] = ((iByte & nBitMask) != 0);
				nBitMask <<= 1;
			}

			int hibit = ((iByte & 0x80) != 0);
			int x     = 0;
			int y     = iByte << 1;

			while (x < 28)
			{
				int adj = (x >= 14) << 1;
				int odd = (x >= 14);

				for (iPixel = 2; iPixel < 9; iPixel++)
				{
					int color = CM_Black;
					if (aPixels[iPixel])
					{
						if (aPixels[iPixel-1] || aPixels[iPixel+1])
							color = CM_White;
						else
							color = ((odd ^ (iPixel&1)) << 1) | hibit;
					}
					else if (aPixels[iPixel-1] && aPixels[iPixel+1])
					{
					    if ((g_eVideoType == VT_COLOR_STANDARD) || (g_eVideoType == VT_COLOR_TVEMU) || !(aPixels[iPixel-2] && aPixels[iPixel+2]))
					        color = ((odd ^ !(iPixel&1)) << 1) | hibit;
					}
						// Colors - Top/Bottom Left/Right
						// cTL cTR
						// cBL cBR
						SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y  ,aColorIndex[color]); // TL
						SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y  ,aColorIndex[color]); // TR
						SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj  ,y+1,aColorIndex[color]); // BL
						SETSOURCEPIXEL(SRCOFFS_HIRES+coloffs+x+adj+1,y+1,aColorIndex[color]); // BR
					x += 2;
				}
			}
		}
	}
}


//===========================================================================
void DrawLoResSource () {
    BYTE colorval[16] = {BLACK,     DEEP_RED, DARK_BLUE, MAGENTA,
            DARK_GREEN,DARK_GRAY,BLUE,      LIGHT_BLUE,
            BROWN,     ORANGE,   LIGHT_GRAY,PINK,
            GREEN,     YELLOW,   AQUA,      WHITE};
    for (int color = 0; color < 16; color++)
        for (int x = 0; x < 16; x++)
            for (int y = 0; y < 16; y++)
                SETSOURCEPIXEL(SRCOFFS_LORES+x,(color << 4)+y,color);
}

//===========================================================================
int GetMonochromeIndex() {

    int iMonochrome;

    switch (g_eVideoType)
    {
    case VT_MONO_AMBER: iMonochrome = MONOCHROME_AMBER ; break;
    case VT_MONO_GREEN: iMonochrome = MONOCHROME_GREEN ; break;
    case VT_MONO_WHITE: iMonochrome = MONOCHROME_WHITE ; break;
    default           : iMonochrome = MONOCHROME_CUSTOM; break;
    }

    return iMonochrome;
}


//===========================================================================
void DrawMonoDHiResSource ()
{
    int iMonochrome = GetMonochromeIndex();

    for (int column = 0; column < 256; column++)
    {
        int coloffs = 10 * column;
        for (unsigned byteval = 0; byteval < 256; byteval++)
        {
            unsigned pattern = MAKEWORD(byteval,column);
            int      y       = byteval << 1;
            for (int x = 0; x < 10; x++)
            {
                BYTE colorval = pattern & (1 << (x+3)) ? iMonochrome : BLACK;
                SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y  ,colorval);
                SETSOURCEPIXEL(SRCOFFS_DHIRES+coloffs+x,y+1,colorval);
            }
        }
    }
}

//===========================================================================
void DrawMonoHiResSource ()
{
    int iMonochrome = GetMonochromeIndex();

    for (int column = 0; column < 512; column += 16)
    {
        for (int y = 0; y < 512; y += 2)
        {
            unsigned val = (y >> 1);
            for (int x = 0; x < 16; x += 2)
            {
                BYTE colorval = (val & 1) ? iMonochrome : BLACK;
                val >>= 1;
                SETSOURCEPIXEL(SRCOFFS_HIRES+column+x  ,y  ,colorval);
                SETSOURCEPIXEL(SRCOFFS_HIRES+column+x+1,y  ,colorval);
                SETSOURCEPIXEL(SRCOFFS_HIRES+column+x  ,y+1,colorval);
                SETSOURCEPIXEL(SRCOFFS_HIRES+column+x+1,y+1,colorval);
            }
        }
    }
}

//===========================================================================
void DrawMonoLoResSource () {
    int iMonochrome = GetMonochromeIndex();

    for (int color = 0; color < 16; color++)
    {
        for (int x = 0; x < 16; x++)
        {
            for (int y = 0; y < 16; y++)
            {
                BYTE colorval = (color >> (x & 3) & 1) ? iMonochrome : BLACK;
                SETSOURCEPIXEL(SRCOFFS_LORES+x,(color << 4)+y,colorval);
            }
        }
    }
}


//===========================================================================
void DrawMonoTextSource ()
{
    BYTE textcolour;
    switch (g_eVideoType)
    {
    case VT_MONO_AMBER: textcolour = MONOCHROME_AMBER; break;
    case VT_MONO_GREEN: textcolour = MONOCHROME_GREEN; break;
    case VT_MONO_WHITE: textcolour = MONOCHROME_WHITE; break;
    default           : textcolour = MONOCHROME_WHITE; break;
    }

    //gu - note that windows bitblts have the origin in the bottom left whereas we are working as if origin is in the top left

    int tempy;
    int tempbyte;
    BYTE byteval;
    int bits; //8 bits per byte
    for (tempy = 0;tempy < 512;tempy++) { //copy rows 0 to 511 of character set into 40COL column of pixels in sourcepixels
        for (tempbyte = 0;tempbyte < 32;tempbyte++) { //we are converting 1bpp bitmap g_pCACharset into an 8bpp bitmap with 0xFF as the non-zero colour
            byteval = g_pCACharset[(((tempy + 256) * 32) + tempbyte)];
            for (bits = 0;bits < 8;bits++) {
                *(g_pSourcePixels + SRCOFFS_40COL + (SRCOFFS_TOTAL * tempy) + (tempbyte * 8) + bits) = ((byteval & 0x80) == 0x80) * textcolour;
                byteval = byteval << 1;
            }
        }
    }

    for (tempy = 0;tempy < 256;tempy++) { //copy rows 513 to 768 of character set into iiplus column of pixels in sourcepixels
        for (tempbyte = 0;tempbyte < 32;tempbyte++) { //we are converting 1bpp bitmap g_pCACharset into an 8bpp bitmap with 0xFF as the non-zero colour
            byteval = g_pCACharset[((tempy * 32) + tempbyte)];
            for (bits = 0;bits < 8;bits++) {
                *(g_pSourcePixels + SRCOFFS_IIPLUS + (SRCOFFS_TOTAL * tempy) + (tempbyte * 8) + bits) = ((byteval & 0x80) == 0x80) * textcolour; //this last byteval bit is sketchy...
                byteval = byteval << 1;
            }
        }
    }

    for (tempy = 0;tempy < 512;tempy++) { //we are copying every second pixel from the characterset into sourcepixels
        for (tempbyte = 0;tempbyte < 32;tempbyte++) { //we are converting 1bpp bitmap g_pCACharset into an 8bpp bitmap with 0xFF as the non-zero colour
            byteval = g_pCACharset[(((tempy + 256) * 32) + tempbyte)];
            for (bits = 0;bits < 3;bits++) {  //only copying every second 1 bit pixel into g_pSourcePixels as 0xFF (so only using 4 bits of the 8 in this byte)
                *(g_pSourcePixels + SRCOFFS_80COL + (SRCOFFS_TOTAL * tempy) + (tempbyte * 4) + bits) = ((byteval & 0x80) == 0x80) * textcolour; //this last byteval bit is sketchy...
                byteval = byteval << 2; //shift it two bits to skip a bit
            }
        }
    }
}

//===========================================================================
void DrawTextSource ()
{

    int tempy;
    int tempbyte;
    BYTE byteval;
    int bits; //8 bits per byte
    for (tempy = 0;tempy < 512;tempy++) { //copy rows 0 to 511 of character set into 40COL column of pixels in sourcepixels
        for (tempbyte = 0;tempbyte < 32;tempbyte++) { //we are converting 1bpp bitmap g_pCACharset into an 8bpp bitmap with 0xFF as the non-zero colour
            byteval = g_pCACharset[(((tempy + 256) * 32) + tempbyte)];
            for (bits = 0;bits < 8;bits++) {
                *(g_pSourcePixels + SRCOFFS_40COL + (SRCOFFS_TOTAL * tempy) + (tempbyte * 8) + bits) = ((byteval & 0x80) == 0x80) * HGR_WHITE;
                byteval = byteval << 1;
            }
        }
    }

	// Chars for Apple ][

    for (tempy = 0;tempy < 256;tempy++) { //copy rows 513 to 768 of character set into iiplus column of pixels in sourcepixels
        for (tempbyte = 0;tempbyte < 32;tempbyte++) { //we are converting 1bpp bitmap g_pCACharset into an 8bpp bitmap with 0xFF as the non-zero colour
            byteval = g_pCACharset[((tempy * 32) + tempbyte)];  //gu - hope this line is right, never tested apple ][
            for (bits = 0;bits < 8;bits++) {
                *(g_pSourcePixels + SRCOFFS_IIPLUS + (SRCOFFS_TOTAL * tempy) + (tempbyte * 8) + bits) = ((byteval & 0x80) == 0x80) * HGR_WHITE; //this last byteval bit is sketchy...
                byteval = byteval << 1;
            }
        }
    }

	// Chars for 80 col mode

	for (tempy = 0;tempy < 512;tempy++) { //copying every second pixel from the characterset into sourcepixels
	    for (tempbyte = 0;tempbyte < 32;tempbyte++) { //converting 1bpp bitmap g_pCACharset into an 8bpp bitmap with 0xFF as the non-zero colour
	        byteval = g_pCACharset[(((tempy + 256) * 32) + tempbyte)];
	        for (bits = 0;bits < 4;bits++) {  //only copying every second 1 bit pixel into g_pSourcePixels as 0xFF (so only using 4 bits of the 8 in this byte)
	            *(g_pSourcePixels + SRCOFFS_80COL + (SRCOFFS_TOTAL * tempy) + (tempbyte * 4) + bits) = ((byteval & 0x80) == 0x80) * HGR_WHITE; //this last byteval bit is sketchy...
	            byteval = byteval << 2; //shift it two bits to skip a bit
	        }
	    }
	}


}

//===========================================================================
void SetLastDrawnImage ()
{
	memcpy(vidlastmem+0x400,g_pTextBank0,0x400);

	if (SW_HIRES)
		memcpy(vidlastmem+0x2000,g_pHiresBank0,0x2000);
	if (SW_DHIRES && SW_HIRES)
		memcpy(vidlastmem,g_pHiresBank1,0x2000);
	else if (SW_80COL)
		memcpy(vidlastmem,g_pTextBank1,0x400);

	int loop;
	for (loop = 0; loop < 256; loop++)
	{
		*(memdirty+loop) &= ~2;
	}
}

//===========================================================================

bool Update40ColCell (int x, int y, int xpixel, int ypixel, int offset) {

    BYTE ch = *(g_pTextBank0+offset); //ch(aracter) = the value in the apple's memory at that location

    bool bCharChanged = (ch != *(vidlastmem+offset+0x400) || g_VideoForceFullRedraw); //does the apple have the same character it did last time?

    // FLASHing chars:
    // - FLASHing if:Alt Char Set is OFF && 0x40<=char<=0x7F
    // - The inverse of this char is located at: char+0x40
    bool bCharFlashing = (g_nAltCharSetOffset == 0) && (ch >= 0x40) && (ch <= 0x7F);

    if(bCharChanged || (bCharFlashing && g_bTextFlashFlag)) {
        bool bInvert = bCharFlashing ? g_bTextFlashState : false;

        CopySource(xpixel,ypixel,
                APPLE_FONT_WIDTH, APPLE_FONT_HEIGHT,
                (IS_APPLE2 ? SRCOFFS_IIPLUS : SRCOFFS_40COL) + ((ch & 0x0F) << 4),
                (ch & 0xF0) + g_nAltCharSetOffset + (bInvert ? 0x40 : 0x00));
        return true;
    }
    return false;
}

inline bool _Update80ColumnCell( BYTE c, const int xPixel, const int yPixel, bool bCharFlashing ) {

    bool bInvert = bCharFlashing ? g_bTextFlashState : false;

    CopySource(
            xPixel, yPixel,
            (APPLE_FONT_WIDTH / 2), APPLE_FONT_HEIGHT,
            SRCOFFS_80COL + ((c & 15)<<3),
            ((c >>4) <<4) + g_nAltCharSetOffset + (bInvert ? 0x40 : 0x00));

    return true;
}

//===========================================================================
bool Update80ColCell (int x, int y, int xpixel, int ypixel, int offset) {

    bool bDirty = false;

    BYTE c1 = *(g_pTextBank1 + offset); // aux
    BYTE c0 = *(g_pTextBank0 + offset); // main

    bool bC1Changed = (c1 != *(vidlastmem + offset +     0) || g_VideoForceFullRedraw);
    bool bC0Changed = (c0 != *(vidlastmem + offset + 0x400) || g_VideoForceFullRedraw);

    bool bC1Flashing = (g_nAltCharSetOffset == 0) && (c1 >= 0x40) && (c1 <= 0x7F);
    bool bC0Flashing = (g_nAltCharSetOffset == 0) && (c0 >= 0x40) && (c0 <= 0x7F);

    if (bC1Changed || (bC1Flashing && g_bTextFlashFlag))
        bDirty = _Update80ColumnCell( c1, xpixel, ypixel, bC1Flashing );

    if (bC0Changed || (bC0Flashing && g_bTextFlashFlag))
        bDirty |= _Update80ColumnCell( c0, xpixel + 7, ypixel, bC0Flashing );

    return bDirty;
}

//===========================================================================
bool UpdateDHiResCell (int x, int y, int xpixel, int ypixel, int offset) {
    bool bDirty = false;
    int  yoffset = 0;
    while (yoffset < 0x2000) {
        BYTE byteval1 = (x >  0) ? *(g_pHiresBank0+offset+yoffset-1) : 0;
        BYTE byteval2 = *(g_pHiresBank1 +offset+yoffset);
        BYTE byteval3 = *(g_pHiresBank0+offset+yoffset);
        BYTE byteval4 = (x < 39) ? *(g_pHiresBank1 +offset+yoffset+1) : 0;
        if ((byteval2 != *(vidlastmem+offset+yoffset)) ||
                (byteval3 != *(vidlastmem+offset+yoffset+0x2000)) ||
                ((x >  0) && ((byteval1 & 0x70) != (*(vidlastmem+offset+yoffset+0x1FFF) & 0x70))) ||
                ((x < 39) && ((byteval4 & 0x07) != (*(vidlastmem+offset+yoffset+     1) & 0x07))) ||
                g_VideoForceFullRedraw) {
            DWORD dwordval = (byteval1 & 0x70)        | ((byteval2 & 0x7F) << 7) |
            ((byteval3 & 0x7F) << 14) | ((byteval4 & 0x07) << 21);
#define PIXEL  0
#define COLOR  ((xpixel + PIXEL) & 3)
#define VALUE  (dwordval >> (4 + PIXEL - COLOR))
            CopySource(xpixel+PIXEL,ypixel+(yoffset >> 9),7,2,
                    SRCOFFS_DHIRES+10*HIBYTE(VALUE)+COLOR,LOBYTE(VALUE)<<1);

#undef PIXEL
#define PIXEL  7
            CopySource(xpixel+PIXEL,ypixel+(yoffset >> 9),7,2,
                    SRCOFFS_DHIRES+10*HIBYTE(VALUE)+COLOR,LOBYTE(VALUE)<<1);

#undef PIXEL
#undef COLOR
#undef VALUE
            bDirty = true;
        }
        yoffset += 0x400;
    }

    return bDirty;
}

//===========================================================================
bool UpdateHiResCell (int x, int y, int xpixel, int ypixel, int offset) {

    bool bDirty  = false;
    int  yoffset = 0;
    while (yoffset < 0x2000) {
        BYTE byteval1 = (x >  0) ? *(g_pHiresBank0+offset+yoffset-1) : 0;
        BYTE byteval2 = *(g_pHiresBank0+offset+yoffset);
        BYTE byteval3 = (x < 39) ? *(g_pHiresBank0+offset+yoffset+1) : 0;
        if ((byteval2 != *(vidlastmem+offset+yoffset+0x2000)) ||
                ((x >  0) && ((byteval1 & 0x60) != (*(vidlastmem+offset+yoffset+0x1FFF) & 0x60))) ||
                ((x < 39) && ((byteval3 & 0x03) != (*(vidlastmem+offset+yoffset+0x2001) & 0x03))) ||
                g_VideoForceFullRedraw) {
#define COLOFFS  (((byteval1 & 0x60) << 2) | \
    ((byteval3 & 0x03) << 5))
            CopySource(xpixel,ypixel+(yoffset >> 9),
                    14,2, //fill 14 pixels wide rather than 7(cause we are 560 when hires is only 280) and 2 high because we are also double height
                    SRCOFFS_HIRES+COLOFFS+((x & 1) << 4),(((int)byteval2) << 1));

#undef COLOFFS
            bDirty = true;
        }
        yoffset += 0x400; //so we end up calling copysource 8 times giving us a 14 wide x 16 high cell (just like update40colcell)
    }

    return bDirty;
}

//===========================================================================
bool UpdateLoResCell (int x, int y, int xpixel, int ypixel, int offset) {
    BYTE val = *(g_pTextBank0+offset);
    if ((val != *(vidlastmem+offset+0x400)) || g_VideoForceFullRedraw) {
        CopySource(xpixel,ypixel,
                14,8,
                SRCOFFS_LORES+((x & 1) << 1),((val & 0xF) << 4));
        CopySource(xpixel,ypixel+8,
                14,8,
                SRCOFFS_LORES+((x & 1) << 1),(val & 0xF0));
        return true;
    }
    return false;
}

//===========================================================================

#define ROL_NIB(x) ( (((x)<<1)&0xF) | (((x)>>3)&1) )

bool UpdateDLoResCell (int x, int y, int xpixel, int ypixel, int offset) {
    BYTE auxval  = *(g_pTextBank1 + offset);
    BYTE mainval = *(g_pTextBank0 + offset);

    if	((auxval != *(vidlastmem+offset)) || (mainval != *(vidlastmem+offset+0x400)) || g_VideoForceFullRedraw ) {
        const BYTE auxval_h = auxval >> 4;
        const BYTE auxval_l = auxval & 0xF;
        auxval = (ROL_NIB(auxval_h)<<4) | ROL_NIB(auxval_l);

        CopySource(	xpixel,ypixel  ,	7,8,SRCOFFS_LORES+((x & 1) << 1),((auxval & 0xF) << 4));
        CopySource(	xpixel,ypixel+8,	7,8,SRCOFFS_LORES+((x & 1) << 1),(auxval & 0xF0));
        CopySource(	xpixel+7,ypixel  , 7,8, SRCOFFS_LORES+((x & 1) << 1),((mainval & 0xF) << 4));
        CopySource(	xpixel+7,ypixel+8, 7,8,	SRCOFFS_LORES+((x & 1) << 1),(mainval & 0xF0));
        return true;
    }
    return false;
}


//
// ----- ALL GLOBALLY ACCESSIBLE FUNCTIONS ARE BELOW THIS LINE -----
//

//===========================================================================
BOOL VideoApparentlyDirty ()
{
    if (SW_MIXED || g_VideoForceFullRedraw)
        return 1;

    DWORD address = (SW_HIRES && !SW_TEXT)
    ? (0x20 << (int)g_bVideoDisplayPage2)
            : (0x04 << (int)g_bVideoDisplayPage2);
    DWORD length  = (SW_HIRES && !SW_TEXT) ? 0x20 : 0x4;
    while (length--)
        if (*(memdirty+(address++)) & 2)
            return 1;

    //

    bool bCharFlashing = false;

    // Scan visible text page for any flashing chars
    if((SW_TEXT || SW_MIXED) && (g_nAltCharSetOffset == 0))
    {
        BYTE* pnMemText = MemGetMainPtr(0x400 << (int)g_bVideoDisplayPage2);

        // Scan 8 long-lines of 120 chars (at 128 char offsets):
        // . Skip 8-char holes in TEXT
        for(UINT y=0; y<8; y++)
        {
            for(UINT x=0; x<40*3; x++)
            {
                BYTE ch = pnMemText[y*128+x];
                if((ch >= 0x40) && (ch <= 0x7F))
                {
                    bCharFlashing = true;
                    break;
                }
            }
        }
    }

    if(bCharFlashing)
        return 1;

    return 0;
}

//===========================================================================

BYTE  VideoCheckMode (WORD, WORD address, BYTE, BYTE, ULONG nCyclesLeft) {
    address &= 0xFF;
    if (address == 0x7F)
        return MemReadFloatingBus(SW_DHIRES != 0, nCyclesLeft);
    else {
        BOOL result = 0;
        switch (address) {
        case 0x1A: result = SW_TEXT;    break;
        case 0x1B: result = SW_MIXED;   break;
        case 0x1D: result = SW_HIRES;   break;
        case 0x1E: result = g_nAltCharSetOffset;   break;
        case 0x1F: result = SW_80COL;   break;
        case 0x7F: result = SW_DHIRES;  break;
        }
        return KeybGetKeycode() | (result ? 0x80 : 0);
    }
}

//===========================================================================
void VideoCheckPage (BOOL force) {
    if ((g_bVideoDisplayPage2 != (SW_PAGE2 != 0)) &&
            (force || (emulmsec-lastpageflip > 500))) {
        g_bVideoDisplayPage2 = (SW_PAGE2 != 0);
        VideoRefreshScreen();
        hasrefreshed = 1;
        lastpageflip = emulmsec;
    }
}

//===========================================================================
BYTE  VideoCheckVbl (WORD, WORD, BYTE, BYTE, ULONG nCyclesLeft) {
	bool bVblBar;
	VideoGetScannerAddress(&bVblBar, nCyclesLeft);
    BYTE r = KeybGetKeycode();
    return (r & ~0x80) | ((bVblBar) ? 0x80 : 0);
 }

//===========================================================================
BOOL VideoHasRefreshed () {
    BOOL result = hasrefreshed;
    hasrefreshed = 0;
    return result;
}

//===========================================================================
void VideoInitialize () {

    if (!CAvidlastmemExists) {
        CAvidlastmemExists = 1;
        // CREATE A BUFFER FOR AN IMAGE OF THE LAST DRAWN MEMORY
        vidlastmem = (LPBYTE)VirtualAlloc(NULL,0x10000,MEM_COMMIT,PAGE_READWRITE);
        CreateDIBSections(); //gu - if we already allocated the memory, then we must have run this before too...
    }

    ZeroMemory(vidlastmem,0x10000);

    // RESET THE VIDEO MODE SWITCHES AND THE CHARACTER SET OFFSET
    VideoResetState();
}

//===========================================================================
void _Video_Dirty()
{
    ZeroMemory(celldirty,40*32);
}

//===========================================================================
void _Video_SetupBanks( bool bBank2 )
{
	g_pHiresBank1 = MemGetAuxPtr (0x2000 << (int)bBank2);
	g_pHiresBank0 = MemGetMainPtr(0x2000 << (int)bBank2);
	g_pTextBank1  = MemGetAuxPtr (0x400  << (int)bBank2);
	g_pTextBank0  = MemGetMainPtr(0x400  << (int)bBank2);
}

//===========================================================================
void VideoRefreshScreen () {
    // CHECK EACH CELL FOR CHANGED BYTES.  REDRAW PIXELS FOR THE CHANGED BYTES
    // IN THE FRAME BUFFER.  MARK CELLS IN WHICH REDRAWING HAS TAKEN PLACE AS
    // DIRTY.

    _Video_Dirty(); //zeroes out the celldirty 2D array
    _Video_SetupBanks( g_bVideoDisplayPage2 );

    VideoUpdateFuncPtr_t pfUpdate = SW_TEXT
    ? SW_80COL
            ? Update80ColCell
                    : Update40ColCell
                    : SW_HIRES
                    ? (SW_DHIRES && SW_80COL)
                            ? UpdateDHiResCell
                                    : UpdateHiResCell
                                    : (SW_DHIRES && SW_80COL)
                                    ? UpdateDLoResCell
                                            : UpdateLoResCell;

    bool bMixed = (SW_MIXED) ? true : false;
    _Video_RedrawScreen( pfUpdate, bMixed );


    if (g_CARedrawFlag > 0) {
        g_CARedrawFlag--;
    }
    if ((g_CARedrawFlag <= 0)) {
        g_VideoForceFullRedraw = 0;
    }
}

//===========================================================================
void _Video_RedrawScreen( VideoUpdateFuncPtr_t pfUpdate, bool bMixed )
{

    BOOL anydirty = 0;
    int  y        = 0;
    int  ypixel   = 0;

    while (y < 20) { //start copying sourcepixels into jni array
        int offset = ((y & 7) << 7) + ((y >> 3) * 40);
        int x      = 0;
        int xpixel = 0;
        while (x < 40) {
            anydirty |= celldirty[x][y] = pfUpdate(x,y,xpixel,ypixel,offset+x);  // gu - <- this line calls update40colcell or updatehirescell or whatever
            ++x;
            xpixel += 14;
        }
        ++y;
        ypixel += 16;
    }

    if( bMixed ) {  //gu - and now do the bottom 4 text lines (separate because may or may not be full screen graphics)
        pfUpdate = SW_80COL
        ? Update80ColCell
                : Update40ColCell;
    }

    while (y < 24) {
        int offset = ((y & 7) << 7) + ((y >> 3) * 40);
        int x      = 0;
        int xpixel = 0;
        while (x < 40) {
            anydirty |= celldirty[x][y] = pfUpdate(x,y,xpixel,ypixel,offset+x);
            ++x;
            xpixel += 14;
        }
        ++y;
        ypixel += 16;
    }
    //gu - now it is writing directly into the jni display array    pfUpdate = UpdatexxxxxCell which calls copysource which writes to the array

    // Clear this flag after TEXT screen has been updated
    g_bTextFlashFlag = false;

    SetLastDrawnImage();
}

//===========================================================================
void VideoResetState () {
    g_nAltCharSetOffset     = 0;
    g_bVideoDisplayPage2 = 0;
    vidmode      = VF_TEXT;
    g_VideoForceFullRedraw   = 1;
}

//===========================================================================
BYTE  VideoSetMode (WORD, WORD address, BYTE write, BYTE, ULONG nCyclesLeft)
{
    address &= 0xFF;
    DWORD oldpage2 = SW_PAGE2;
    int   oldvalue = g_nAltCharSetOffset+(int)(vidmode & ~(VF_MASK2 | VF_PAGE2));
    switch (address) {
    case 0x00: vidmode &= ~VF_MASK2;   break;
    case 0x01: vidmode |=  VF_MASK2;   break;
    case 0x0C: if (!IS_APPLE2) vidmode &= ~VF_80COL;   break;
    case 0x0D: if (!IS_APPLE2) vidmode |=  VF_80COL;   break;
    case 0x0E: if (!IS_APPLE2) g_nAltCharSetOffset = 0;           break;	// Alternate char set off
    case 0x0F: if (!IS_APPLE2) g_nAltCharSetOffset = 256;         break;	// Alternate char set on
    case 0x50: vidmode &= ~VF_TEXT;    break;
    case 0x51: vidmode |=  VF_TEXT;    break;
    case 0x52: vidmode &= ~VF_MIXED;   break;
    case 0x53: vidmode |=  VF_MIXED;   break;
    case 0x54: vidmode &= ~VF_PAGE2;   break;
    case 0x55: vidmode |=  VF_PAGE2;   break;
    case 0x56: vidmode &= ~VF_HIRES;   break;
    case 0x57: vidmode |=  VF_HIRES;   break;
    case 0x5E: if (!IS_APPLE2) vidmode |=  VF_DHIRES;  break;
    case 0x5F: if (!IS_APPLE2) vidmode &= ~VF_DHIRES;  break;
    }
    if (SW_MASK2)
        vidmode &= ~VF_PAGE2;
    if (oldvalue != g_nAltCharSetOffset+(int)(vidmode & ~(VF_MASK2 | VF_PAGE2))) {
        graphicsmode = !SW_TEXT;
        g_VideoForceFullRedraw   = 1;
    }
    if (g_bFullSpeed && oldpage2 && !SW_PAGE2) {
        oldpage2 = SW_PAGE2;
    }
    if (oldpage2 != SW_PAGE2) {
        static DWORD lastrefresh = 0;
        if ((g_bVideoDisplayPage2 && !SW_PAGE2) || (!0)) {  //gu - (!0) was (!behind) but behind is always set to zero - keeping here to track the change
            g_bVideoDisplayPage2 = (SW_PAGE2 != 0);
            if (!g_VideoForceFullRedraw) {
                VideoRefreshScreen();
                hasrefreshed = 1;
                lastrefresh  = emulmsec;
            }
        }
        else if ((!SW_PAGE2) && (!g_VideoForceFullRedraw) && (emulmsec-lastrefresh >= 20)) {
            g_bVideoDisplayPage2 = 0;
            VideoRefreshScreen();
            hasrefreshed = 1;
            lastrefresh  = emulmsec;
        }
        lastpageflip = emulmsec;
    }
    return MemReadFloatingBus(nCyclesLeft);
}

//===========================================================================

void VideoUpdateVbl (DWORD dwCyclesThisFrame)
{
	dwVBlCounter = (DWORD) ((double)dwCyclesThisFrame / (double)uCyclesPerLine);
}

//===========================================================================

// Called at 60Hz (every 16.666ms)
void VideoUpdateFlash() {
    static UINT nTextFlashCnt = 0;

    // Flash rate:
    // . NTSC : 60/16 ~= 4Hz
    // . PAL  : 50/16 ~= 3Hz
    nTextFlashCnt = (nTextFlashCnt+1) & 0xf;

    // BUG: In unthrottled CPU mode, flash rate should not be affected
    if(nTextFlashCnt == 0)
    {
        g_bTextFlashState = !g_bTextFlashState;

        // Redraw any FLASHing chars if any text showing. NB. No FLASH g_nAppMode for 80 cols
        if ((SW_TEXT || SW_MIXED) ) // && !SW_80COL) // FIX: FLASH 80-Column
            g_bTextFlashFlag = true;
    }
}

//===========================================================================
//
// References to Jim Sather's books are given as eg:
// UTAIIe:5-7,P3 (Understanding the Apple IIe, chapter 5, page 7, Paragraph 3)
//
WORD VideoGetScannerAddress(bool* pbVblBar_OUT, const DWORD uExecutedCycles)
{
    // get video scanner position
    //
    int nCycles = CpuGetCyclesThisFrame(uExecutedCycles);

    // machine state switches
    //
    int nHires   = (SW_HIRES && !SW_TEXT) ? 1 : 0;
    int nPage2   = (SW_PAGE2) ? 1 : 0;
    int n80Store = (MemGet80Store()) ? 1 : 0;

    // calculate video parameters according to display standard
    //
    int nScanLines  = bVideoScannerNTSC ? kNTSCScanLines : kPALScanLines;
    int nVSyncLine  = bVideoScannerNTSC ? kNTSCVSyncLine : kPALVSyncLine;
    int nScanCycles = nScanLines * kHClocks;

    // calculate horizontal scanning state
    //
    int nHClock = (nCycles + kHPEClock) % kHClocks; // which horizontal scanning clock
    int nHState = kHClock0State + nHClock; // H state bits
    if (nHClock >= kHPresetClock) // check for horizontal preset
    {
        nHState -= 1; // correct for state preset (two 0 states)
    }
    int h_0 = (nHState >> 0) & 1; // get horizontal state bits
    int h_1 = (nHState >> 1) & 1;
    int h_2 = (nHState >> 2) & 1;
    int h_3 = (nHState >> 3) & 1;
    int h_4 = (nHState >> 4) & 1;
    int h_5 = (nHState >> 5) & 1;

    // calculate vertical scanning state
    //
    int nVLine  = nCycles / kHClocks; // which vertical scanning line
    int nVState = kVLine0State + nVLine; // V state bits
    if ((nVLine >= kVPresetLine)) // check for previous vertical state preset
    {
        nVState -= nScanLines; // compensate for preset
    }
    int v_A = (nVState >> 0) & 1; // get vertical state bits
    int v_B = (nVState >> 1) & 1;
    int v_C = (nVState >> 2) & 1;
    int v_0 = (nVState >> 3) & 1;
    int v_1 = (nVState >> 4) & 1;
    int v_2 = (nVState >> 5) & 1;
    int v_3 = (nVState >> 6) & 1;
    int v_4 = (nVState >> 7) & 1;
    int v_5 = (nVState >> 8) & 1;

    // calculate scanning memory address
    //
    if (nHires && SW_MIXED && v_4 && v_2) // HIRES TIME signal (UTAIIe:5-7,P3)
    {
        nHires = 0; // address is in text memory for mixed hires
    }

    int nAddend0 = 0x0D; // 1            1            0            1
    int nAddend1 =              (h_5 << 2) | (h_4 << 1) | (h_3 << 0);
    int nAddend2 = (v_4 << 3) | (v_3 << 2) | (v_4 << 1) | (v_3 << 0);
    int nSum     = (nAddend0 + nAddend1 + nAddend2) & 0x0F; // SUM (UTAIIe:5-9)

    int nAddress = 0; // build address from video scanner equations (UTAIIe:5-8,T5.1)
    nAddress |= h_0  << 0; // a0
    nAddress |= h_1  << 1; // a1
    nAddress |= h_2  << 2; // a2
    nAddress |= nSum << 3; // a3 - a6
    nAddress |= v_0  << 7; // a7
    nAddress |= v_1  << 8; // a8
    nAddress |= v_2  << 9; // a9

    int p2a = !(nPage2 && !n80Store);
    int p2b = nPage2 && !n80Store;

    if (nHires) // hires?
    {
        // Y: insert hires-only address bits
        //
        nAddress |= v_A << 10; // a10
        nAddress |= v_B << 11; // a11
        nAddress |= v_C << 12; // a12
        nAddress |= p2a << 13; // a13
        nAddress |= p2b << 14; // a14
    }
    else
    {
        // N: insert text-only address bits
        //
        nAddress |= p2a << 10; // a10
        nAddress |= p2b << 11; // a11

        // Apple ][ (not //e) and HBL?
        //
        if (IS_APPLE2 && // Apple II only (UTAIIe:I-4,#5)
                !h_5 && (!h_4 || !h_3)) // HBL (UTAIIe:8-10,F8.5)
        {
            nAddress |= 1 << 12; // Y: a12 (add $1000 to address!)
        }
    }

    // update VBL' state
    //
    if (pbVblBar_OUT != NULL)
    {
        *pbVblBar_OUT = !v_4 || !v_3; // VBL' = (v_4 & v_3)' (UTAIIe:5-10,#3)
    }
    return static_cast<WORD>(nAddress);
}

//===========================================================================

DWORD VideoGetSnapshot(SS_IO_Video* pSS)
{
    pSS->bAltCharSet = !(g_nAltCharSetOffset == 0);
    pSS->dwVidMode = vidmode;
    return 0;
}

//===========================================================================

DWORD VideoSetSnapshot(SS_IO_Video* pSS)
{
    g_nAltCharSetOffset = !pSS->bAltCharSet ? 0 : 256;
    vidmode = pSS->dwVidMode;
    graphicsmode = !SW_TEXT;
    g_bVideoDisplayPage2 = (SW_PAGE2 != 0);
    return 0;
}


